/**
 * @class Ext.ElementLoader
 * A class used to load remote content to an Element. Sample usage:
 * <pre><code>
Ext.get('el').load({
    url: 'myPage.php',
    scripts: true,
    params: {
        id: 1
    }
});
 * </code></pre>
 * <p>
 * In general this class will not be instanced directly, rather the {@link Ext.core.Element#load} method
 * will be used.
 * </p>
 */
Ext.define('Ext.ElementLoader', {

    /* Begin Definitions */

    mixins: {
        observable: 'Ext.util.Observable'
    },

    uses: [
        'Ext.data.Connection',
        'Ext.Ajax'
    ],
    
    statics: {
        Renderer: {
            Html: function(loader, response, active){
                loader.getTarget().update(response.responseText, active.scripts === true);
                return true;
            }
        }     
    },

    /* End Definitions */

    /**
     * @cfg {String} url The url to retrieve the content from. Defaults to <tt>null</tt>.
     */
    url: null,

    /**
     * @cfg {Object} params Any params to be attached to the Ajax request. These parameters will
     * be overridden by any params in the load options. Defaults to <tt>null</tt>.
     */
    params: null,

    /**
     * @cfg {Object} baseParams Params that will be attached to every request. These parameters
     * will not be overridden by any params in the load options. Defaults to <tt>null</tt>.
     */
    baseParams: null,

    /**
     * @cfg {Boolean/Object} autoLoad True to have the loader make a request as soon as it is created. Defaults to <tt>false</tt>.
     * This argument can also be a set of options that will be passed to {@link #load} is called.
     */
    autoLoad: false,

    /**
     * @cfg {Mixed} target The target element for the loader. It can be the DOM element, the id or an Ext.Element.
     */
    target: null,

    /**
     * @cfg {Mixed} loadMask True or a string to show when the element is loading.
     */
    loadMask: false,

    /**
     * @cfg {Object} ajaxOptions Any additional options to be passed to the request, for example timeout or headers. Defaults to <tt>null</tt>.
     */
    ajaxOptions: null,
    
    /**
     * @cfg {Boolean} scripts True to parse any inline script tags in the response.
     */
    scripts: false,

    /**
     * @cfg {Function} success A function to be called when a load request is successful.
     */

    /**
     * @cfg {Function} failure A function to be called when a load request fails.
     */

    /**
     * @cfg {Object} scope The scope to execute the {@link #success} and {@link #failure} functions in.
     */
    
    /**
     * @cfg {Function} renderer A custom function to render the content to the element. The passed parameters
     * are
     * <ul>
     * <li>The loader</li>
     * <li>The response</li>
     * <li>The active request</li>
     * </ul>
     */

    isLoader: true,

    constructor: function(config) {
        var me = this,
            autoLoad;
        
        config = config || {};
        Ext.apply(me, config);
        me.setTarget(me.target);
        me.addEvents(
            /**
             * @event beforeload
             * Fires before a load request is made to the server.
             * Returning false from an event listener can prevent the load
             * from occurring.
             * @param {Ext.ElementLoader} this
             * @param {Object} options The options passed to the request
             */
            'beforeload',

            /**
             * @event exception
             * Fires after an unsuccessful load.
             * @param {Ext.ElementLoader} this
             * @param {Object} response The response from the server
             * @param {Object} options The options passed to the request
             */
            'exception',

            /**
             * @event exception
             * Fires after a successful load.
             * @param {Ext.ElementLoader} this
             * @param {Object} response The response from the server
             * @param {Object} options The options passed to the request
             */
            'load'
        );

        // don't pass config because we have already applied it.
        me.mixins.observable.constructor.call(me);

        if (me.autoLoad) {
            autoLoad = me.autoLoad;
            if (autoLoad === true) {
                autoLoad = {};
            }
            me.load(autoLoad);
        }
    },

    /**
     * Set an {Ext.Element} as the target of this loader. Note that if the target is changed,
     * any active requests will be aborted.
     * @param {Mixed} target The element
     */
    setTarget: function(target){
        var me = this;
        target = Ext.get(target);
        if (me.target && me.target != target) {
            me.abort();
        }
        me.target = target;
    },

    /**
     * Get the target of this loader.
     * @return {Ext.Component} target The target, null if none exists.
     */
    getTarget: function(){
        return this.target || null;
    },

    /**
     * Aborts the active load request
     */
    abort: function(){
        var active = this.active;
        if (active !== undefined) {
            Ext.Ajax.abort(active.request);
            if (active.mask) {
                this.removeMask();
            }
            delete this.active;
        }
    },
    
    /**
     * Remove the mask on the target
     * @private
     */
    removeMask: function(){
        this.target.unmask();
    },
    
    /**
     * Add the mask on the target
     * @private
     * @param {Mixed} mask The mask configuration
     */
    addMask: function(mask){
        this.target.mask(mask === true ? null : mask);
    },

    /**
     * Load new data from the server.
     * @param {Object} options The options for the request. They can be any configuration option that can be specified for
     * the class, with the exception of the target option. Note that any options passed to the method will override any
     * class defaults.
     */
    load: function(options) {
        //<debug>
        if (!this.target) {
            Ext.Error.raise('A valid target is required when loading content');
        }
        //</debug>

        options = Ext.apply({}, options);

        var me = this,
            target = me.target,
            mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
            params = Ext.apply({}, options.params),
            ajaxOptions = Ext.apply({}, options.ajaxOptions),
            callback = options.callback || me.callback,
            scope = options.scope || me.scope || me,
            request;

        Ext.applyIf(ajaxOptions, me.ajaxOptions);
        Ext.applyIf(options, ajaxOptions);

        Ext.applyIf(params, me.params);
        Ext.apply(params, me.baseParams);

        Ext.applyIf(options, {
            url: me.url
        });

        //<debug>
        if (!options.url) {
            Ext.Error.raise('You must specify the URL from which content should be loaded');
        }
        //</debug>

        Ext.apply(options, {
            scope: me,
            params: params,
            callback: me.onComplete
        });

        if (me.fireEvent('beforeload', me, options) === false) {
            return;
        }

        if (mask) {
            me.addMask(mask);
        }

        request = Ext.Ajax.request(options);
        me.active = {
            request: request,
            options: options,
            mask: mask,
            scope: scope,
            callback: callback,
            success: options.success || me.success,
            failure: options.failure || me.failure,
            renderer: options.renderer || me.renderer,
            scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
        };
        me.setOptions(me.active, options);
    },
    
    /**
     * Set any additional options on the active request
     * @private
     * @param {Object} active The active request
     * @param {Object} options The initial options
     */
    setOptions: Ext.emptyFn,

    /**
     * Parse the response after the request completes
     * @private
     * @param {Object} options Ajax options
     * @param {Boolean} success Success status of the request
     * @param {Object} response The response object
     */
    onComplete: function(options, success, response) {
        var me = this,
            active = me.active,
            scope = active.scope,
            renderer = me.getRenderer(active.renderer);


        if (success) {
            success = renderer.call(me, me, response, active);
        }

        if (success) {
            Ext.callback(active.success, scope, [me, response, options]);
            me.fireEvent('load', me, response, options);
        } else {
            Ext.callback(active.failure, scope, [me, response, options]);
            me.fireEvent('exception', me, response, options);
        }
        Ext.callback(active.callback, scope, [me, success, response, options]);

        if (active.mask) {
            me.removeMask();
        }

        delete me.active;
    },

    /**
     * Gets the renderer to use
     * @private
     * @param {String/Function} renderer The renderer to use
     * @return {Function} A rendering function to use.
     */
    getRenderer: function(renderer){
        if (Ext.isFunction(renderer)) {
            return renderer;
        }
        return this.statics().Renderer.Html;
    },
    
    /**
     * Automatically refreshes the content over a specified period.
     * @param {Number} interval The interval to refresh in ms.
     * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
     */
    startAutoRefresh: function(interval, options){
        var me = this;
        me.stopAutoRefresh();
        me.autoRefresh = setInterval(function(){
            me.load(options);
        }, interval);
    },
    
    /**
     * Clears any auto refresh. See {@link #startAutoRefresh}.
     */
    stopAutoRefresh: function(){
        clearInterval(this.autoRefresh);
        delete this.autoRefresh;
    },
    
    /**
     * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
     * @return {Boolean} True if the loader is automatically refreshing
     */
    isAutoRefreshing: function(){
        return Ext.isDefined(this.autoRefresh);
    },

    /**
     * Destroys the loader. Any active requests will be aborted.
     */
    destroy: function(){
        var me = this;
        me.stopAutoRefresh();
        delete me.target;
        me.abort();
        me.clearListeners();
    }
});
